/************************************************************
       (@_       
  _     ) \_______________________________________________
 (_)@8@8{}<_______________________________________________>
        )_/        
       (@
Liuxp
Website:        http://me.alipay.com/liuxp
Version：       2.3
File name：     Publish.cs
Author：        刘小平
Date：          ‎2010年‎03‎月‎17‎日，‏‎19:54:40
Description：   VS 发布工具
History：       在 AssemblyInfo.cs 里面的 AssemblyDescription 中查看
************************************************************/
using System;
using System.IO;
using System.Linq;
using System.Data;
using Publish.Enum;
using Publish.Services;
using System.Threading;
using System.Windows.Forms;
using System.Collections.Generic;

namespace Publish
{
    public partial class Publish : Form
    {
        #region Fields(全局变量)

        #region Public

        public static readonly String[] Files = { "Web.Debug.config", "Web.Release.config" };//排除的文件
        public static readonly String[] FilesExtension = { ".csproj", ".cs", ".vb", ".user", ".vspscc", ".pdb", ".pubxml" };//排除的文件类型
        public static readonly String[] Folder = { "obj", ".svn", "logs", "Avatar" };//排除的文件夹
        //排除的文件夹  这种方式可能会出现排除功能性文件夹2012.01.11 16:21 || 2012.11.01 09:53 果然出现了。

        public static String Versions = String.Empty;//最新版本 文件夹名称
        public static DateTime CurrentDate = DateTime.Now.AddDays(-1);//当前日期，用于标记当前版本号，减一天是为了第一次运行时执行
        public static String DesktopPath = String.Empty;//桌面所在地址

        public static String Path = String.Empty;//当前源码所在位置
        public static IDictionary<Char, List<Files>> DictionaryList;//对比文件列表

        #endregion

        #region Private

        private Thread publishThread;//发布线程
        private String lastModifiedTime;//最后修改时间，用于判断是否需要重新加载项目文件版本信息

        private readonly Object historyMutexObj = new Object();//线程锁

        private readonly int threadId = 0;//线程ID

        private Int32 width, height;//用于控制窗体的宽、高
        private const String Fewer = "▲Fewer &details";
        private const String More = "▼More &details";

        private readonly System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();//计时专用，系统函数
        private XmlService xmlService;
        private readonly FileService fileService = new FileService();

        #endregion

        #endregion

        #region Constructors

        public Publish()
        {
            #region 将C盘的配置文件迁移到D盘

            var folderPath = "D:/Program Files/Config";//配置文件路径
            var fileName = "D:/Program Files/Config/Publish.config";

            var oldFileName = "C:/Program Files/Config/Publish.config";

            var directoryInfo = new DirectoryInfo(folderPath);//获取文件路径信息

            if (!directoryInfo.Exists) directoryInfo.Create();//如果文件夹不存在

            if (!File.Exists(fileName) && File.Exists(oldFileName))
            {
                File.Move(oldFileName, fileName);
            }

            #endregion

            CheckForIllegalCrossThreadCalls = false;

            InitializeComponent();

            InitServerSocketUdp();//初始化服务端

            threadId = Thread.CurrentThread.GetHashCode();//线程Id赋值

            fileService.ShowCurrentItem += new FileService.OnPublishHander(ShowCurrentItem);
        }

        #endregion

        #region 属性

        /// <summary>
        /// 项目名称，根据源码位置动态截取
        /// </summary>
        public static String ProjectName
        {
            get
            {
                return string.IsNullOrWhiteSpace(Path)
                    ? string.Empty
                    : Path.Substring(Path.LastIndexOf('\\') + 1);
            }
        }

        //项目名称，根据源码位置动态截取

        #endregion

        #region 窗体事件

        //窗体加载
        private void Publish_Load(object sender, EventArgs e)
        {
            width = 530;// 宽
            height = 0xd1;//209 高

            btnStop.Location = btnPub.Location;//将发布按钮和终止按钮重合在一起
            btnStop.Enabled = false;//在未开始发布前禁用 btnStop 按钮，因为该按钮会和 btnSelect 快捷键冲突

            DesktopPath = new DesktopPathService().GetDesktopPath();//获取桌面路径

            #region 线程处理（提高程序启动速度）

            Service.CheckTableThreadStart();//验证表是否存在，初始华环境

            BindDataThreadStart();//绑定项目地址数据

            BindHistoryThreadStart();//读取发布历史列表

            #endregion

            Publish_SizeChanged(sender, e);
        }

        //固定窗体大小
        private void Publish_SizeChanged(object sender, EventArgs e)
        {
            Width = width;
            Height = height;
        }

        //按键事件
        private void Publish_KeyDown(object sender, KeyEventArgs e)
        {
            var keyValue = e.KeyValue;

            switch (keyValue)
            {
                case 82: // 82 按键 R
                    if (e.Alt)
                    {
                        if (btnOpen.Text == Fewer)//当展开时，读取发布历史列表
                            BindHistoryThreadStart();
                        else
                            PublishService.BindPublishCountThreadStart();//读取当前项目发布次数

                        //最后修改时间和当前记录的时间不一致，可以重新初始化项目文件版本信息
                        if (GetLastModifiedTimeState())
                            InitDictionaryListThreadStart(); //初始化项目文件版本信息
                    }
                    break;
                case 112: // 112 按键 F1
                    new AboutBox().ShowDialog(); //显示关于窗体。  
                    break;
            }
        }

        //关闭窗体
        private void Publish_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (MessageBox.Show("Do you want to exit？", "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question) ==
                DialogResult.No)
            {
                e.Cancel = true;//取消退出
            }
            else
            {
                //关闭监听
                SocketUdpClient.Broadcast(DatagramType.OffLine);
                socketUdpServer.Stop();
            }
        }

        #endregion

        #region 按钮事件

        //发布按钮 - 完整发布
        private void btnPub_Click(object sender, EventArgs e)
        {
            //var configuration = xmlUtility.GetValue(Path + "/" + ProjName + ".csproj");
            //if (configuration == "Debug")
            //{
            //    if (MessageBox.Show("the solution configuration is Debug \r\n you want to continue?", "Information", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.No) return;
            //}

            if (!CheckPath()) return;//验证项目地址是否有问题

            #region 验证是否是一个有效的项目地址（一个没有项目的地址）

            //var flag = true;

            var source = new DirectoryInfo(Path);//目标路径（项目所在路径）
            if (!source.Exists)
            {
                MessageBox.Show("项目路径不存在，看看是不是已经搬家了。", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;//原路径找不到鸟~~~
            }
            //var files = source.GetFiles();//获取文件

            //for (var i = 0; i < files.Length; i++)
            //{
            //    if (files[i].Extension == ".csproj")
            //    {
            //        flag = false;//是排除文件类型
            //        break;
            //    }
            //}

            //if (flag)
            //{
            //    MessageBox.Show("额，，，这不是一个有效的项目路径！", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //    return;
            //}

            #endregion

            sw.Start();//计时开始
            btnPub.SendToBack();//切换按钮
            btnStop.Enabled = true;//启用停止按钮

            ddlProjectPath.Enabled = false;//正在发布中，禁用项目地址

            var desktopFolders = FolderService.CreatedFolder(ProjectName);//创建文件夹用于存放最新一个版本

            publishThread = new Thread(() => fileService.CopyDirectory(Path, desktopFolders)) { IsBackground = true };//开启复制线程
            publishThread.Start();//创建文件后就开始复制文件

            (new Thread(() => ShowTime()) { IsBackground = true }).Start();//显示时间

            if (CurrentDate.Day != DateTime.Now.Day)
            {
                // 当软件运行很多天一直没有退出，会遇到这种情况。
                CleanFolderThreadStart();//清除今天以前的版本（清理桌面）
            }

            SetPath();//保存项目地址信息
        }

        //发布按钮 - 小版本发布
        private void btnSelectFile_Click(object sender, EventArgs e)
        {
            if (!CheckPath()) return;//验证项目地址是否有问题

            new SelectFile().ShowDialog();

            //if (IsPublish) //现处于局域网广播状态中，故暂时不需要重复初始化版本信息
            //{
            //    InitDictionaryListThreadStart();//重新初始化项目文件版本信息
            //    IsPublish = false;//重置标记
            //}

            InitDictionaryListThreadStart();//重新初始化项目文件版本信息

            SetPath();//保存项目地址信息
        }

        //终止按钮
        private void btnStop_Click(object sender, EventArgs e)
        {
            if (publishThread == null) return;

            publishThread.Abort();

            sw.Stop();//停止记时
            sw.Reset();//重置时间，下次就从0开始了
            btnPub.BringToFront();//切换按钮，将发布按钮放在上面
            ddlProjectPath.Enabled = true;//终止发布，重新启用项目地址

            ShowCurrentItem("（User Stop this Publish）");//
        }

        //展开/收缩按钮  显示发布列表记录
        private void btnOpen_Click(object sender, EventArgs e)
        {
            if (btnOpen.Text == More)//折叠状态，准备展开
            {
                btnOpen.Text = Fewer;
                lvHistory.Visible = true;
                height = 317;//高，设置新高度以显示发布记录列表

                Broadcast();//发布完成，进行广播，以同步数据

                //BindHistoryThreadStart();//读取发布历史列表
            }
            else//展开状态、准备折叠
            {
                btnOpen.Text = More;
                lvHistory.Visible = false;
                height = 209;//高，恢复原来高度

                PublishService.BindPublishCountThreadStart();//读取当前项目发布次数
            }

            Publish_SizeChanged(sender, e);
        }

        #endregion

        #region 下拉框事件 comboBox

        //改变项目地址，重新加载项目文件版本信息
        private void ddlProjectPath_SelectedIndexChanged(object sender, EventArgs e)
        {
            Path = ddlProjectPath.Text.Trim(); //源码所在位置，在发布按钮事件下还会再赋值一次

            InitDictionaryListThreadStart();//初始化项目文件版本信息

            PublishService.BindPublishCountThreadStart();//读取当前项目发布次数
        }
        //comboBox 删除项目地址
        private void ddlProjectPath_KeyDown(object sender, KeyEventArgs e)
        {
            var path = ddlProjectPath.Text;

            // 46 Delete 按钮
            if (e.KeyValue == 46 && !String.IsNullOrWhiteSpace(path) && Directory.Exists(path)
                && (MessageBox.Show("确定删除【" + ProjectName + "】项目地址吗？", "消息提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes))
            {
                RemovePath(path);
            }
        }

        //ToolTip
        private void ddlProjectPath_MouseHover(object sender, EventArgs e)
        {
            toolTip1.Show(" ", ddlProjectPath);
        }

        #endregion

        #region Timer 事件

        //数据监控 Timer —— 每10分钟扫描一次判断是否重新加载
        private void timer1_Tick(object sender, EventArgs e)
        {
            //最后修改时间和当前记录的时间不一致，可以重新初始化项目文件版本信息
            if (GetLastModifiedTimeState())
                InitDictionaryListThreadStart();//重新初始化项目文件版本信息
        }

        #endregion

        #region 右键菜单 事件

        //删除项目路径
        private void deleteProjectPathMenuItem_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("确定删除【" + ProjectName + "】项目地址吗？", "消息提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                RemovePath(ddlProjectPath.Text);
            }
        }

        //排序 using 引用
        private void sortUsingMenuItem_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrWhiteSpace(Path))
            {
                var sortUsingThread = new Thread(() => fileService.SortUsing(ddlProjectPath.Text)) { IsBackground = true };
                sortUsingThread.Start();//排序 Class 中的 using 引用
            }
        }

        //统计代码行数
        private void totalCodeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var totalLineService  = new TotalLineService();
            totalLineService.ShowCurrentItem += new TotalLineService.OnCompleteHander(ShowCurrentItem);
            
            totalLineService.GetTotalLine(ddlProjectPath.Text.Trim());
        }

        //刷新
        private void refreshToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BindHistoryThreadStart();//读取发布历史列表
        }

        //查询视图
        private void newQueryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new NewQuery().Show();
        }

        #endregion

        #region Method 方法

        #region Publish

        /// <summary>
        /// 发布完成，进行广播，以同步数据
        /// </summary>
        public static void Broadcast()
        {
            //if (SocketUdpClient == null) SocketUdpClient = new SocketUdpClient();//可能会出现在休眠后，先使用一天再说。
            SocketUdpClient.Broadcast(DatagramType.Broadcast);//发布完成，进行广播，以同步数据
        }

        #endregion

        #region Private

        /// <summary>
        /// 读取发布历史列表
        /// </summary>
        private void BindHistory()
        {
            lock (historyMutexObj)
            {
                lvHistory.Items.Clear();//清除现有list数据，准备接收新的数据

                using (var table = PublishService.GetHistory())
                {
                    if (table == null) return;

                    ListView ListViewWithToolTips = new ListView { ShowItemToolTips = true };

                    foreach (DataRow item in table.Rows)
                    {
                        AddLvItem(new[]
                        {
                            item["Id"].ToString() // Id（No.）
                            , item["FolderName"].ToString() // FolderName
                            , item["Type"].ToString() == "1" ? "F" : "S" // Type 
                            , item["CreatorIPv4"].ToString() // CreatorIPv4
                            , item["CreatorHostName"].ToString() // CreatorHostName
                            , Convert.ToDateTime(item["CreatedTime"].ToString()).ToString("HH:mm:ss") // CreatedTime
                            , item["Files"].ToString() // 
                        }, lvHistory);

                    }
                    //Controls.Add(ListViewWithToolTips);
                    foreach (ListViewItem item in lvHistory.Items)
                    {//加 ToolTip 悬浮提示
                        //toolTip1.Show("Version：" + lvi.SubItems[1].Text
                        //    + "\r\nFiles:\r\n　" + lvi.SubItems[6].Text.Replace("|", "\r\n　"), ListViewWithToolTips);
                        if (item == null) continue;
                        item.ToolTipText = "Version：" + item.SubItems[1].Text
                            + "\r\nFiles：\r\n　" + item.SubItems[6].Text.Replace("|", "\r\n　");
                    }
                }
            }
        }

        /// <summary>
        /// 获取项目文件版本信息最后修改时间，以判断是否需要重新初始化项目文件版本信息
        /// </summary>
        /// <returns></returns>
        private Boolean GetLastModifiedTimeState()
        {
            var lastModifiedTimeByProjectName = PublishFileVersionService.GetLastModifiedTimeByProjectName(ProjectName);

            if (String.IsNullOrWhiteSpace(lastModifiedTimeByProjectName) || lastModifiedTimeByProjectName.Equals(lastModifiedTime))
            {
                lastModifiedTime = lastModifiedTimeByProjectName;//记录当前修改时间
                return true;
            }
            return false;
        }

        /// <summary>
        /// 绑定项目地址数据
        /// </summary>
        private void BindData()
        {
            if (xmlService == null) xmlService = new XmlService();

            lock (xmlService)
            {
                ddlProjectPath.SelectedIndexChanged -= ddlProjectPath_SelectedIndexChanged; //取消 IndexChanged 事件

                var list = xmlService.GetAllProject();

                var firstOrDefault = list.FirstOrDefault(x => x.IsDefault == "1"); //取出最后发布项目的记录

                ddlProjectPath.DataSource = list;
                ddlProjectPath.ValueMember = "Id";
                ddlProjectPath.DisplayMember = "Path";

                if (firstOrDefault != null)
                {
                    ddlProjectPath.SelectedValue = firstOrDefault.Id; //设置默认选中项，获取上次发布的项目地址
                    Path = firstOrDefault.Path; //源码所在位置，在发布按钮事件下还会再赋值一次
                }
                ddlProjectPath.SelectedIndexChanged += ddlProjectPath_SelectedIndexChanged; //注册 IndexChanged 事件

                InitDictionaryListThreadStart(); //初始化项目文件版本信息

                PublishService.BindPublishCountThreadStart();//读取当前项目发布次数
            }

            if (CurrentDate.Day != DateTime.Now.Day)//作为第一次启动运行，之所以放在这里是因为需要在项目地址绑定完成后才能执行清除，造成这样的结局主要是为了让主窗体代码量减少
                CleanFolderThreadStart();//清除今天以前的版本（清理桌面）
        }

        /// <summary>
        /// 初始化项目文件版本信息
        /// </summary>
        private void InitDictionaryList()
        {
            Path = ddlProjectPath.Text.Trim(); //源码所在位置，在发布按钮事件下还会再赋值一次

            lock (ProjectName)
            {
                DictionaryList = PublishFileVersionService.GetAllFilesByProjectName(ProjectName); //获取当前项目之前文件版本信息
            }
        }

        /// <summary>
        /// 显示耗时，及记录当前发布版本号
        /// </summary>
        private void ShowTime()
        {
            SetControlText("Time：00:00:00", lblTime);//显示程序已用时
            var date = new DateTime();
            while (publishThread != null && publishThread.ThreadState != ThreadState.Stopped)//判断复制线程状态
            {
                Thread.Sleep(500);
                var time = "Time：" + date.AddMilliseconds(sw.ElapsedMilliseconds).ToString("HH:mm:ss");
                SetControlText(time, lblTime);//显示程序已用时
            }

            var desktopFolders = DesktopPath + Versions;
            FolderService.ClearEmptyFolder(desktopFolders);//清理空文件夹

            ShowCurrentItem("（Publish succeeded，Package Create...）");//将发布文件打包，便于传输

            fileService.CompressRar(desktopFolders, DesktopPath, String.Format("{0}.zip", Versions));//生成zip压缩包

            ShowCurrentItem("（Publish succeeded，The file in the desktop）");//告诉用户发布完成了。

            sw.Stop();//停止记时
            sw.Reset();//重置时间，下次就从0开始了

            PublishFileVersionService.UpdateThreadStart();//更新项目文件版本数据

            FolderService.SaveFolder(Versions, 1);//将当前版本号及发布者信息记录到数据库

            PublishService.BindPublishCountThreadStart();//读取当前项目发布次数

            btnPub.BringToFront();//切换按钮
            ddlProjectPath.Enabled = true;//发布完成，重新启用项目地址
            btnStop.Enabled = false;//在未开始发布前禁用 btnStop 按钮，因为该按钮会和 btnSelect 快捷键冲突
        }

        /// <summary>
        /// 获取项目地址
        /// </summary>
        private void GetPath()
        {
            if (Path.ToUpper() != ddlProjectPath.Text.Trim().ToUpper())
            {
                Path = ddlProjectPath.Text.Trim(); //特殊情况，这里需要单独赋值一次
                InitDictionaryListThreadStart();//重新输入的新项目路径，初始化项目文件版本信息
            }

            var list = ddlProjectPath.Items.Cast<Project>();

            foreach (var project in list.Where(item => item.Path == Path))
            {
                ddlProjectPath.Text = project.Path;
                ddlProjectPath.SelectedItem = project.Id;
            }
        }

        /// <summary>
        /// 尝试保存新增的项目路径，或更新默认选中项
        /// </summary>
        private void SetPath()
        {
            var list = ddlProjectPath.Items.Cast<Project>();

            var entity = new Project
            {
                IsDefault = "1",//设置为默认地址
                Path = Path,
            };

            var item = list.FirstOrDefault(x => x.Path.ToUpper() == Path.ToUpper());

            if (item != null && item.IsDefault == "1") return;

            if (ddlProjectPath.SelectedValue == null)//还没有选择
            {//新增
                entity.Id = Guid.NewGuid().ToString();
                xmlService.Create(entity);//持久化保存
            }
            else
            {//更新，设置默认项
                if (ddlProjectPath.SelectedValue != null)
                    entity.Id = ddlProjectPath.SelectedValue.ToString();
                entity.Path = Path;

                xmlService.Modify(entity);//持久化保存
            }

            //刷新数据
            BindDataThreadStart();//绑定项目地址数据
        }

        /// <summary>
        /// 验证项目地址是否有问题
        /// </summary>
        /// <returns></returns>
        private Boolean CheckPath()
        {
            GetPath();//获取项目地址信息

            if (String.IsNullOrEmpty(Path))
            {
                MessageBox.Show("The path is empty！", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }
            if (Path.Length > 1000 || !Directory.Exists(Path))// 一个超长的地址，或则地址不存在
            {
                MessageBox.Show("The path is error！", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 移除项目地址
        /// </summary>
        /// <param name="path"></param>
        private void RemovePath(String path)
        {
            //执行删除项目地址操作，
            xmlService.Remove(ddlProjectPath.SelectedIndex != -1
                ? ddlProjectPath.SelectedValue.ToString()
                : path);

            BindDataThreadStart();//绑定项目地址数据
        }

        /// <summary>
        /// 显示当前项信息
        /// </summary>
        /// <param name="sender"></param>
        private void ShowCurrentItem(String sender)
        {
            SetControlText("Current Item：" + sender, lblCurrentItem);//正在复制的文件名称
        }

        #region Thread Method

        /// <summary>
        /// 初始化项目文件版本信息
        /// </summary>
        private void InitDictionaryListThreadStart()
        {
            var initDictionaryListThread = new Thread(InitDictionaryList) { IsBackground = true };
            initDictionaryListThread.Start();//初始化项目文件版本信息
        }

        /// <summary>
        /// 绑定项目地址数据
        /// </summary>
        private void BindDataThreadStart()
        {
            var bindDataThread = new Thread(BindData) { IsBackground = true };
            bindDataThread.Start();//绑定项目地址数据
        }

        /// <summary>
        /// 读取发布历史列表
        /// </summary>
        private void BindHistoryThreadStart()
        {
            //这里先读取一次发布历史是为了先成功的与数据库建立连接，以解决后面查看时卡屏的问题
            var bindHistoryThread = new Thread(BindHistory) { IsBackground = true };
            bindHistoryThread.Start();//读取发布历史列表
        }

        /// <summary>
        /// 清除今天以前的版本（清理桌面）
        /// </summary>
        private void CleanFolderThreadStart()
        {
            var list = ddlProjectPath.Items.Cast<Project>();//获取当前列表中的数据

            var cleanFolderThread = new Thread(() => FolderService.CleanFolder(list)) { IsBackground = true };
            cleanFolderThread.Start();//清除今天以前的版本（清理桌面）
        }

        #endregion

        #endregion

        #endregion

        #region 通用委托事件

        #region 含Text属性的控件 委托事件

        private delegate void SetControlTextDelegate(String value, Control control);
        event SetControlTextDelegate SetText;
        /// <summary>
        /// 委托调用方法
        /// </summary>
        /// <param name="value"></param>
        /// <param name="control"></param>
        internal void SetControlText(String value, Control control)
        {
            if (Thread.CurrentThread.GetHashCode() != threadId)
            {
                SetText = new SetControlTextDelegate(SetControlTextHandler);
                Invoke(SetText, new Object[] { value, control });
            }
            else SetControlTextHandler(value, control);
        }
        internal void SetControlTextHandler(String value, Control control)
        {
            control.Text = value;
        }

        #endregion

        #region ListView 委托事件

        private delegate void AddLvItemDelegate(String[] value, ListView control);
        event AddLvItemDelegate AddItem;
        /// <summary>
        /// 委托调用方法
        /// </summary>
        /// <param name="value"></param>
        /// <param name="control"></param>
        internal void AddLvItem(String[] value, ListView control)
        {
            if (Thread.CurrentThread.GetHashCode() != threadId)
            {
                AddItem = new AddLvItemDelegate(AddLvItemHandler);
                Invoke(AddItem, new Object[] { value, control });
            }
            else AddLvItemHandler(value, control);
        }
        internal void AddLvItemHandler(String[] value, ListView control)
        {
            control.Items.Add(new ListViewItem(value));
        }

        #endregion

        #endregion
    }
}
